// Copyright (c) 2010 Omer Goshen

#ifndef __GenericCamera_H__
#define __GenericCamera_H__

#include <Ogre.h>
#include <OIS.h>
//#include <boost/signal.hpp>

#include "MudBath.h"

#include "core/Countable.h"
#include "managers/InputManager.h"
#include "utils/ProceduralPrimitiveFactory.h"

using namespace Ogre;

///TODO: Drop SceneNode inheritance...


//{{{ GenericCamera
/**
 * \class GenericCamera
 * \author gershon
 */
class GenericCamera :
	public SceneNode,
	public FrameListener,
	public Countable<GenericCamera>
{
public:
	typedef std::map<int, GenericCamera*> CameraMapId;

	GenericCamera(SceneManager* sceneMgr);
	virtual ~GenericCamera();

	//virtual bool frameRenderingQueued(const FrameEvent& evt) = 0;
	virtual bool frameStarted(const FrameEvent& evt) = 0;
	virtual bool frameEnded(const FrameEvent& evt) = 0;

	static GenericCamera* getCameraById(int id) { return GenericCamera::CamerasById[id]; }

	inline void attachCamera(Ogre::Camera* cam, GenericCamera* oldCam=NULL)
	{
//		if(isCameraAttached(cam)) return;

//		if(oldCam!=NULL && !oldCam->onCamDetached.empty())
//			{
//				oldCam->onCamDetached();
//			}

		mCamera = cam;
		if(mCamera->getParentSceneNode()!=NULL)
			mCamera->getParentSceneNode()->detachObject(mCamera);
		attachObject(mCamera);

		saveCamPos();

//		if(!onCamAttached.empty())
//			onCamAttached();
	}

	const Ogre::Camera* getCamera() const {	return mCamera; }
	inline Ogre::Camera* getCamera() { return mCamera; }
	inline bool isCameraAttached(Ogre::Camera* c) { return  mCamera != 0 && mCamera == c; }

	void draw();

	inline void setDebugNodeVisible(bool v) { mDebugNode->setVisible(v); }

	static CameraMapId CamerasById;
	static int numCameras() { return howMany(); }

//	boost::signal<void ()> onCamAttached;
//	boost::signal<void ()> onCamDetached;

	SceneNode* mDebugNode;

	inline void saveCamPos() { mOldCamPos = getCamera()->getDerivedPosition(); }
	inline void restoreCamPos() { getCamera()->setPosition(mOldCamPos); }
	inline void clearCamPtr() { mCamera = 0; }

protected:
	size_t mId;
	SceneManager* mSceneMgr;
	Entity* mEntity;
	Ogre::MaterialPtr mMaterial;
	Ogre::Camera* mCamera;
	Vector3 mOldCamPos;
	Real mOrthoZoom;
};
//}}}

//{{{ FlyCamera
class FlyCamera : public GenericCamera
{
public:
	enum Mode
	{
		FREE_LOOK,
		MOUSE_LOOK,
		MOUSE_MOVE
	};

	SceneNode* target;

	Vector3 mForceVector;
	Vector3 mAccTransVector;
	Real mMove;
	Real mCurrentSpeed;

	bool lockLookAt;
	bool lockUpAxis;

	bool freeLook;
	bool useMouseButton;
	bool slowMove;

//	Overlay* overlay;
//	OverlayContainer* crosshair;

	FlyCamera(SceneManager* sceneMgr) :
		GenericCamera(sceneMgr),
		mForceVector(Vector3::ZERO),
		mAccTransVector(Vector3::ZERO),
		mMove(0.0125f),
		lockLookAt(false),
		lockUpAxis(true),
		freeLook(true),
		useMouseButton(false),
		slowMove(false)
	{
//        mCamera->setPosition(Vector3::ZERO);
//        mCamera->yaw(Degree(180));
//		setPosition(Vector3(20, 20, -20));

		/*
		///TODO: static?
				OverlayManager& olm = OverlayManager::getSingleton();
				overlay = olm.create("CrossHair");
				crosshair=static_cast<OverlayContainer*>(olm.createOverlayElement("Panel","crosshair"));
				crosshair->setMetricsMode(Ogre::GMM_PIXELS);
				RenderWindow* win = Root::getSingleton().getAutoCreatedWindow();
				crosshair->setPosition((win->getWidth()-45)/2, (win->getHeight()-45)/2);
				crosshair->setDimensions(45, 45);
				crosshair->setMaterialName("CrossHair");
				overlay->add2D(crosshair);
		//			overlay->setZOrder(100);
		//			overlay->show();
		 */

	}

	~FlyCamera() {}

	void onKeyUp(const OIS::KeyEvent &e);
	void onKeyDown(const OIS::KeyEvent &e);
	void onMouseMove(const OIS::MouseEvent &e);

	bool frameRenderingQueued(const FrameEvent& evt);
	bool frameStarted(const FrameEvent& evt);
	bool frameEnded(const FrameEvent& evt);


};
//}}}

//{{{ ChaseCamera
class ChaseCamera : public GenericCamera
{
public:
	SceneNode *target;

	Real mTopSpeed;
	Real mRotateSpeed;
	bool useSpring;

	Vector3 upVector;
	Vector3 mAccel;
	Vector3 mVelocity;

	ChaseCamera(SceneManager* sceneMgr) : GenericCamera(sceneMgr),
		target(0),
		mTopSpeed(100.f),
		mRotateSpeed(0),
		useSpring(false),
		upVector(Vector3::UNIT_Y),
		mAccel(Vector3::ZERO),
		mVelocity(Vector3::ZERO)
	{}

	~ChaseCamera() {}

	bool frameStarted(const FrameEvent& evt);
	bool frameEnded(const FrameEvent& evt);
	bool frameRenderingQueued(const FrameEvent& evt);
};
//}}}

//{{{ OrbitCamera
class OrbitCamera : public GenericCamera
{
public:
	SceneNode *target;

	OrbitCamera(SceneManager* sceneMgr) : GenericCamera(sceneMgr),
		target(0),
		mUseKey(true),
		mUsePress(true),
		mUseMiddle(true),
		mMove(false)
	{
//		onCamAttached.connect(boost::bind(&GenericCamera::restoreCamPos, this));
	}

	virtual ~OrbitCamera()
	{
		Root::getSingleton().removeFrameListener(this);
	}

	void onMousePress(const OIS::MouseEvent &e);
	void onMouseRelease(const OIS::MouseEvent &e);
	void onMouseMove(const OIS::MouseEvent &e);

	void update();

	bool frameStarted(const FrameEvent& evt);
	bool frameEnded(const FrameEvent& evt);
//		bool frameRenderingQueued(const FrameEvent& evt);


	Vector3 mLastMouse;
	Vector3 mDeltaMouse;
	Vector3 mTargetPos;

	bool mUseKey;
	bool mUsePress;
	bool mUseMiddle;
	bool mMove;

};
//}}}


#endif
